package ooo.reindeer.commons.concurrent;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class WorkerPool {

    TokenManager tokenManager;
    ExecutorService workerExecutor;

    int maxThread = 1;

    public WorkerPool(int maxThread) {
        this(maxThread, null);
    }

    public WorkerPool(int maxThread, String name) {

        this.maxThread = maxThread;
        if (maxThread == 0) {
            workerExecutor = Executors.newCachedThreadPool(new NamedThreadFactory(name));
        } else {
            tokenManager = new TokenManager(maxThread);
            workerExecutor = new ThreadPoolExecutor(maxThread, maxThread, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new NamedThreadFactory(name));
            for (int i = 0; i < maxThread; i++) {
                tokenManager.generateToken();
            }
        }
    }

    public <W extends WorkTask<T>, T> List<Future<T>> allWorkerExecute(W task, Object... params) {
        List<Future<T>> futureList = new ArrayList<Future<T>>();
        for (int i = 0; i < maxThread; i++) {
            futureList.add(execute(task, params));
        }
        return futureList;
    }

    public <W extends WorkTask<T>, T> Future<T> execute(W task, Object... params) {
        return execute(task, new Watch<W, T>(), params);
    }

    public <W extends WorkTask<T>, T> Future<T> execute(W task, Watch<W, T> watch, Object... params) {
        Token token = null;
        if (tokenManager != null) {
            try {
                token = tokenManager.borrow();
            } catch (InterruptedException e) {
            }
        }
        Worker<T, W> worker = new Worker<T, W>(token, task, watch, params);
        return workerExecutor.submit(worker);
    }

    public void shutdown() {
        workerExecutor.shutdown();
        try {
            workerExecutor.awaitTermination(30,TimeUnit.SECONDS);
        } catch (InterruptedException e) {
        }
    }

    public void shutdownNow() {
        workerExecutor.shutdownNow();
        try {
            workerExecutor.awaitTermination(30,TimeUnit.SECONDS);
        } catch (InterruptedException e) {
        }
    }

    static class Token {

        BlockingQueue<Token> workers;

        private Token(BlockingQueue<Token> workers) {
            this.workers = workers;
            workers.add(this);
        }

        public void release() {
            workers.add(this);
        }
    }

    class TokenManager {
        LinkedBlockingQueue<Token> workers;

        TokenManager(int count) {
            workers = new LinkedBlockingQueue<Token>(count);
        }

        Token borrow() throws InterruptedException {
            return workers.take();
        }

        Token generateToken() {
            return new Token(workers);
        }

    }

    class Worker<T, W extends WorkTask<T>> implements Callable<T> {

        final W task;
        final Object[] params;
        final Token token;
        final Watch<W, T> watch;

        Worker(Token token, W task, Watch<W, T> watch, Object... params) {
            this.token = token;
            this.task = task;
            this.params = params;
            this.watch = watch;
        }

        private void afterWork(T returnObject) {
            if (watch != null) {
                try {
                    watch.afterWork(task, returnObject, params);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        private void beforeWork() {
            if (watch != null) {
                try {
                    watch.beforeWork(task, params);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public T call() {
            try {
                beforeWork();
                T returnObject = task.task(params);
                afterWork(returnObject);
                return returnObject;
            } catch (Throwable throwable) {
                try {
                    return onThrow(throwable);
                } catch (Throwable throwable1) {
                    throw new RuntimeException(throwable);
                }
            } finally {
                if (token != null) {
                    token.release();
                }
            }

        }

        private T onThrow(Throwable throwable) throws Throwable {
            if (watch != null) {
                T returnObject = watch.onThrow(task, throwable, params);
                afterWork(returnObject);
                return returnObject;
            } else {
                throw throwable;
            }
        }
    }

}


